/*
 * Copyright (C) 2009 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.collect.testing;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.collect.testing.features.CollectionFeature;
import com.google.common.collect.testing.features.CollectionSize;
import com.google.common.collect.testing.features.SetFeature;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CopyOnWriteArraySet;

import junit.framework.Test;
import junit.framework.TestSuite;

/**
 * Generates a test suite covering the {@link Set} implementations in the {@link java.util} package.
 * Can be subclassed to specify tests that should be suppressed.
 *
 * @author Kevin Bourrillion
 */
@GwtIncompatible
public class TestsForSetsInJavaUtil
{
    public static Test suite()
    {
        return new TestsForSetsInJavaUtil().allTests();
    }

    public Test allTests()
    {
        TestSuite suite = new TestSuite("java.util Sets");
        suite.addTest(testsForEmptySet());
        suite.addTest(testsForSingletonSet());
        suite.addTest(testsForHashSet());
        suite.addTest(testsForLinkedHashSet());
        suite.addTest(testsForEnumSet());
        suite.addTest(testsForTreeSetNatural());
        suite.addTest(testsForTreeSetWithComparator());
        suite.addTest(testsForCopyOnWriteArraySet());
        suite.addTest(testsForUnmodifiableSet());
        suite.addTest(testsForCheckedSet());
        suite.addTest(testsForCheckedSortedSet());
        suite.addTest(testsForAbstractSet());
        suite.addTest(testsForBadlyCollidingHashSet());
        suite.addTest(testsForConcurrentSkipListSetNatural());
        suite.addTest(testsForConcurrentSkipListSetWithComparator());

        return suite;
    }

    protected Collection<Method> suppressForEmptySet()
    {
        return Collections.emptySet();
    }

    protected Collection<Method> suppressForSingletonSet()
    {
        return Collections.emptySet();
    }

    protected Collection<Method> suppressForHashSet()
    {
        return Collections.emptySet();
    }

    protected Collection<Method> suppressForLinkedHashSet()
    {
        return Collections.emptySet();
    }

    protected Collection<Method> suppressForEnumSet()
    {
        return Collections.emptySet();
    }

    protected Collection<Method> suppressForTreeSetNatural()
    {
        return Collections.emptySet();
    }

    protected Collection<Method> suppressForTreeSetWithComparator()
    {
        return Collections.emptySet();
    }

    protected Collection<Method> suppressForCopyOnWriteArraySet()
    {
        return Collections.emptySet();
    }

    protected Collection<Method> suppressForUnmodifiableSet()
    {
        return Collections.emptySet();
    }

    protected Collection<Method> suppressForCheckedSet()
    {
        return Collections.emptySet();
    }

    protected Collection<Method> suppressForCheckedSortedSet()
    {
        return Collections.emptySet();
    }

    protected Collection<Method> suppressForAbstractSet()
    {
        return Collections.emptySet();
    }

    protected Collection<Method> suppressForConcurrentSkipListSetNatural()
    {
        return Collections.emptySet();
    }

    protected Collection<Method> suppressForConcurrentSkipListSetWithComparator()
    {
        return Collections.emptySet();
    }

    public Test testsForEmptySet()
    {
        return SetTestSuiteBuilder.using(
                        new TestStringSetGenerator()
                        {
                            @Override
                            public Set<String> create(String[] elements)
                            {
                                return Collections.emptySet();
                            }
                        })
                .named("emptySet")
                .withFeatures(CollectionFeature.SERIALIZABLE, CollectionSize.ZERO)
                .suppressing(suppressForEmptySet())
                .createTestSuite();
    }

    public Test testsForSingletonSet()
    {
        return SetTestSuiteBuilder.using(
                        new TestStringSetGenerator()
                        {
                            @Override
                            public Set<String> create(String[] elements)
                            {
                                return Collections.singleton(elements[0]);
                            }
                        })
                .named("singleton")
                .withFeatures(
                        CollectionFeature.SERIALIZABLE,
                        CollectionFeature.ALLOWS_NULL_VALUES,
                        CollectionSize.ONE)
                .suppressing(suppressForSingletonSet())
                .createTestSuite();
    }

    public Test testsForHashSet()
    {
        return SetTestSuiteBuilder.using(
                        new TestStringSetGenerator()
                        {
                            @Override
                            public Set<String> create(String[] elements)
                            {
                                return new HashSet<>(MinimalCollection.of(elements));
                            }
                        })
                .named("HashSet")
                .withFeatures(
                        SetFeature.GENERAL_PURPOSE,
                        CollectionFeature.SERIALIZABLE,
                        CollectionFeature.ALLOWS_NULL_VALUES,
                        CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
                        CollectionSize.ANY)
                .suppressing(suppressForHashSet())
                .createTestSuite();
    }

    public Test testsForLinkedHashSet()
    {
        return SetTestSuiteBuilder.using(
                        new TestStringSetGenerator()
                        {
                            @Override
                            public Set<String> create(String[] elements)
                            {
                                return new LinkedHashSet<>(MinimalCollection.of(elements));
                            }
                        })
                .named("LinkedHashSet")
                .withFeatures(
                        SetFeature.GENERAL_PURPOSE,
                        CollectionFeature.SERIALIZABLE,
                        CollectionFeature.ALLOWS_NULL_VALUES,
                        CollectionFeature.KNOWN_ORDER,
                        CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
                        CollectionSize.ANY)
                .suppressing(suppressForLinkedHashSet())
                .createTestSuite();
    }

    public Test testsForEnumSet()
    {
        return SetTestSuiteBuilder.using(
                        new TestEnumSetGenerator()
                        {
                            @Override
                            public Set<AnEnum> create(AnEnum[] elements)
                            {
                                return (elements.length == 0)
                                        ? EnumSet.noneOf(AnEnum.class)
                                        : EnumSet.copyOf(MinimalCollection.of(elements));
                            }
                        })
                .named("EnumSet")
                .withFeatures(
                        SetFeature.GENERAL_PURPOSE,
                        CollectionFeature.SERIALIZABLE,
                        CollectionFeature.KNOWN_ORDER,
                        CollectionFeature.RESTRICTS_ELEMENTS,
                        CollectionSize.ANY)
                .suppressing(suppressForEnumSet())
                .createTestSuite();
    }

    public Test testsForTreeSetNatural()
    {
        return NavigableSetTestSuiteBuilder.using(
                        new TestStringSortedSetGenerator()
                        {
                            @Override
                            public SortedSet<String> create(String[] elements)
                            {
                                return new TreeSet<>(MinimalCollection.of(elements));
                            }
                        })
                .named("TreeSet, natural")
                .withFeatures(
                        SetFeature.GENERAL_PURPOSE,
                        CollectionFeature.SERIALIZABLE,
                        CollectionFeature.KNOWN_ORDER,
                        CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
                        CollectionSize.ANY)
                .suppressing(suppressForTreeSetNatural())
                .createTestSuite();
    }

    public Test testsForTreeSetWithComparator()
    {
        return NavigableSetTestSuiteBuilder.using(
                        new TestStringSortedSetGenerator()
                        {
                            @Override
                            public SortedSet<String> create(String[] elements)
                            {
                                SortedSet<String> set = new TreeSet<>(arbitraryNullFriendlyComparator());
                                Collections.addAll(set, elements);
                                return set;
                            }
                        })
                .named("TreeSet, with comparator")
                .withFeatures(
                        SetFeature.GENERAL_PURPOSE,
                        CollectionFeature.SERIALIZABLE,
                        CollectionFeature.ALLOWS_NULL_VALUES,
                        CollectionFeature.KNOWN_ORDER,
                        CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
                        CollectionSize.ANY)
                .suppressing(suppressForTreeSetWithComparator())
                .createTestSuite();
    }

    public Test testsForCopyOnWriteArraySet()
    {
        return SetTestSuiteBuilder.using(
                        new TestStringSetGenerator()
                        {
                            @Override
                            public Set<String> create(String[] elements)
                            {
                                return new CopyOnWriteArraySet<>(MinimalCollection.of(elements));
                            }
                        })
                .named("CopyOnWriteArraySet")
                .withFeatures(
                        CollectionFeature.SUPPORTS_ADD,
                        CollectionFeature.SUPPORTS_REMOVE,
                        CollectionFeature.SERIALIZABLE,
                        CollectionFeature.ALLOWS_NULL_VALUES,
                        CollectionFeature.KNOWN_ORDER,
                        CollectionSize.ANY)
                .suppressing(suppressForCopyOnWriteArraySet())
                .createTestSuite();
    }

    public Test testsForUnmodifiableSet()
    {
        return SetTestSuiteBuilder.using(
                        new TestStringSetGenerator()
                        {
                            @Override
                            public Set<String> create(String[] elements)
                            {
                                Set<String> innerSet = new HashSet<>();
                                Collections.addAll(innerSet, elements);
                                return Collections.unmodifiableSet(innerSet);
                            }
                        })
                .named("unmodifiableSet/HashSet")
                .withFeatures(
                        CollectionFeature.NONE,
                        CollectionFeature.SERIALIZABLE,
                        CollectionFeature.ALLOWS_NULL_VALUES,
                        CollectionSize.ANY)
                .suppressing(suppressForUnmodifiableSet())
                .createTestSuite();
    }

    public Test testsForCheckedSet()
    {
        return SetTestSuiteBuilder.using(
                        new TestStringSetGenerator()
                        {
                            @Override
                            public Set<String> create(String[] elements)
                            {
                                Set<String> innerSet = new HashSet<>();
                                Collections.addAll(innerSet, elements);
                                return Collections.checkedSet(innerSet, String.class);
                            }
                        })
                .named("checkedSet/HashSet")
                .withFeatures(
                        SetFeature.GENERAL_PURPOSE,
                        CollectionFeature.SERIALIZABLE,
                        CollectionFeature.ALLOWS_NULL_VALUES,
                        CollectionFeature.RESTRICTS_ELEMENTS,
                        CollectionSize.ANY)
                .suppressing(suppressForCheckedSet())
                .createTestSuite();
    }

    public Test testsForCheckedSortedSet()
    {
        return SortedSetTestSuiteBuilder.using(
                        new TestStringSortedSetGenerator()
                        {
                            @Override
                            public SortedSet<String> create(String[] elements)
                            {
                                SortedSet<String> innerSet = new TreeSet<>();
                                Collections.addAll(innerSet, elements);
                                return Collections.checkedSortedSet(innerSet, String.class);
                            }
                        })
                .named("checkedSortedSet/TreeSet, natural")
                .withFeatures(
                        SetFeature.GENERAL_PURPOSE,
                        CollectionFeature.KNOWN_ORDER,
                        CollectionFeature.SERIALIZABLE,
                        CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
                        CollectionFeature.RESTRICTS_ELEMENTS,
                        CollectionSize.ANY)
                .suppressing(suppressForCheckedSortedSet())
                .createTestSuite();
    }

    public Test testsForAbstractSet()
    {
        return SetTestSuiteBuilder.using(
                        new TestStringSetGenerator()
                        {
                            @Override
                            protected Set<String> create(String[] elements)
                            {
                                final String[] deduped = dedupe(elements);
                                return new AbstractSet<String>()
                                {
                                    @Override
                                    public int size()
                                    {
                                        return deduped.length;
                                    }

                                    @Override
                                    public Iterator<String> iterator()
                                    {
                                        return MinimalCollection.of(deduped).iterator();
                                    }
                                };
                            }
                        })
                .named("AbstractSet")
                .withFeatures(
                        CollectionFeature.NONE,
                        CollectionFeature.ALLOWS_NULL_VALUES,
                        CollectionFeature.KNOWN_ORDER, // in this case, anyway
                        CollectionSize.ANY)
                .suppressing(suppressForAbstractSet())
                .createTestSuite();
    }

    public Test testsForBadlyCollidingHashSet()
    {
        return SetTestSuiteBuilder.using(
                        new TestCollidingSetGenerator()
                        {
                            @Override
                            public Set<Object> create(Object... elements)
                            {
                                return new HashSet<>(MinimalCollection.of(elements));
                            }
                        })
                .named("badly colliding HashSet")
                .withFeatures(
                        SetFeature.GENERAL_PURPOSE,
                        CollectionFeature.ALLOWS_NULL_VALUES,
                        CollectionSize.SEVERAL)
                .suppressing(suppressForHashSet())
                .createTestSuite();
    }

    public Test testsForConcurrentSkipListSetNatural()
    {
        return SetTestSuiteBuilder.using(
                        new TestStringSortedSetGenerator()
                        {
                            @Override
                            public SortedSet<String> create(String[] elements)
                            {
                                return new ConcurrentSkipListSet<>(MinimalCollection.of(elements));
                            }
                        })
                .named("ConcurrentSkipListSet, natural")
                .withFeatures(
                        SetFeature.GENERAL_PURPOSE,
                        CollectionFeature.SERIALIZABLE,
                        CollectionFeature.KNOWN_ORDER,
                        CollectionSize.ANY)
                .suppressing(suppressForConcurrentSkipListSetNatural())
                .createTestSuite();
    }

    public Test testsForConcurrentSkipListSetWithComparator()
    {
        return SetTestSuiteBuilder.using(
                        new TestStringSortedSetGenerator()
                        {
                            @Override
                            public SortedSet<String> create(String[] elements)
                            {
                                SortedSet<String> set =
                                        new ConcurrentSkipListSet<>(arbitraryNullFriendlyComparator());
                                Collections.addAll(set, elements);
                                return set;
                            }
                        })
                .named("ConcurrentSkipListSet, with comparator")
                .withFeatures(
                        SetFeature.GENERAL_PURPOSE,
                        CollectionFeature.SERIALIZABLE,
                        CollectionFeature.KNOWN_ORDER,
                        CollectionSize.ANY)
                .suppressing(suppressForConcurrentSkipListSetWithComparator())
                .createTestSuite();
    }

    private static String[] dedupe(String[] elements)
    {
        Set<String> tmp = new LinkedHashSet<>();
        Collections.addAll(tmp, elements);
        return tmp.toArray(new String[0]);
    }

    static <T> Comparator<T> arbitraryNullFriendlyComparator()
    {
        return new NullFriendlyComparator<T>();
    }

    private static final class NullFriendlyComparator<T> implements Comparator<T>, Serializable
    {
        @Override
        public int compare(T left, T right)
        {
            return String.valueOf(left).compareTo(String.valueOf(right));
        }
    }
}
